/*
 * Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * https://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.icesoft.framework.core.starter;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.icesoft.framework.core.base.BaseHandlerInterceptor;
import com.icesoft.framework.core.converter.*;
import com.icesoft.framework.core.data.ProtectFieldHandler;
import com.icesoft.framework.core.data.encrypt.ProtectInterceptor;
import com.icesoft.framework.core.handler.DataAccessControlInterceptor;
import com.icesoft.framework.core.util.D;
import lombok.RequiredArgsConstructor;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Nullable;
import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

/**
 * Core自动配置类
 */
@EnableAsync
@Configuration
@EnableConfigurationProperties({GlobalProperties.class})
@MapperScan({"com.gitee.sunchenbin.mybatis.actable.dao.*", "com.icesoft.**.mapper"})
@ComponentScan(basePackages = {"com.gitee.sunchenbin.mybatis.actable.manager.*", "com.icesoft.*"})
@RequiredArgsConstructor
public class CoreAutoConfig implements WebMvcConfigurer {
	private static final Logger log = LoggerFactory.getLogger(CoreAutoConfig.class);

	private final List<BaseHandlerInterceptor> interceptors;

	@Value("${spring.jackson.date-format:" + D.FORMAT_DATETIME_Y4MDHMS + "}")
	private String defaultDatePattern;

	@Value("${spring.jackson.time-zone:GMT+8}")
	private String defaultTimeZone;

	@Value("${spring.jackson.default-property-inclusion:NON_NULL}")
	private JsonInclude.Include defaultPropertyInclusion;

	@Bean
	public CorsFilter corsFilter() {
		//1. 添加 CORS配置信息
		CorsConfiguration config = new CorsConfiguration();
		//放行哪些原始域
		config.addAllowedOriginPattern("*");
		//是否发送 Cookie
		config.setAllowCredentials(false);
		//放行哪些请求方式
		config.addAllowedMethod("*");
		//放行哪些原始请求头部信息
		config.addAllowedHeader("*");
		//暴露哪些头部信息
		config.addExposedHeader("*");
		//2. 添加映射路径
		UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
		corsConfigurationSource.registerCorsConfiguration("/**", config);
		//3. 返回新的CorsFilter
		return new CorsFilter(corsConfigurationSource);
	}

	/**
	 * 默认配置 ObjectMapper, 并允许用户覆盖
	 *
	 * @return Jackson2ObjectMapperBuilderCustomizer
	 */
	@Bean
	@ConditionalOnMissingBean
	public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
		return builder -> {
			// Long转换成String避免JS超长问题
			builder.serializerByType(Long.class, ToStringSerializer.instance);
			builder.serializerByType(Long.TYPE, ToStringSerializer.instance);
			builder.serializerByType(BigInteger.class, ToStringSerializer.instance);

			// 支持java8时间类型
			// LocalDateTime
			DateTimeFormatter localDateTimeFormatter = DateTimeFormatter.ofPattern(D.FORMAT_DATETIME_Y4MDHMS);
			builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(localDateTimeFormatter));
			builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(localDateTimeFormatter));
			// LocalDate
			DateTimeFormatter localDateFormatter = DateTimeFormatter.ofPattern(D.FORMAT_DATE_Y4MD);
			builder.serializerByType(LocalDate.class, new LocalDateSerializer(localDateFormatter));
			builder.deserializerByType(LocalDate.class, new LocalDateDeserializer(localDateFormatter));
			// LocalTime
			DateTimeFormatter localTimeFormatter = DateTimeFormatter.ofPattern(D.FORMAT_TIME_HHmmss);
			builder.serializerByType(LocalTime.class, new LocalTimeSerializer(localTimeFormatter));
			builder.deserializerByType(LocalTime.class, new LocalTimeDeserializer(localTimeFormatter));

			// 设置序列化包含策略
			builder.serializationInclusion(defaultPropertyInclusion);
			// 时间格式化
			builder.failOnUnknownProperties(false);
			builder.timeZone(TimeZone.getTimeZone(defaultTimeZone));
			SimpleDateFormat dateFormat = new SimpleDateFormat(defaultDatePattern) {
				@Override
				public Date parse(String dateStr) {
					return D.fuzzyConvert(dateStr);
				}
			};
			builder.dateFormat(dateFormat);
		};
	}

	@Bean
	@ConditionalOnMissingBean
	public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
		Jackson2ObjectMapperBuilder objectMapperBuilder = new Jackson2ObjectMapperBuilder();
		jsonCustomizer().customize(objectMapperBuilder);
		log.info("启用icesoft默认的Jackson自定义配置");
		return objectMapperBuilder;
	}

	@Bean
	@ConditionalOnMissingBean
	public MappingJackson2HttpMessageConverter jacksonMessageConverter() {
		return new MappingJackson2HttpMessageConverter(jackson2ObjectMapperBuilder().build());
	}

	/**
	 * Mybatis-plus分页插件
	 */
	@Bean
	@ConditionalOnMissingBean
	public MybatisPlusInterceptor mybatisPlusInterceptor() {
		MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
		// 分页拦截器
		interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
		// 数据权限拦截器
		interceptor.addInnerInterceptor(new DataAccessControlInterceptor());
		return interceptor;
	}

	/**
	 * 数据保护拦截器
	 * <p>
	 * 默认不注入，core.enable-data-protect=true可开启
	 */
	@Bean
	@ConditionalOnBean(ProtectFieldHandler.class)
	public ProtectInterceptor protectInterceptor() {
		return new ProtectInterceptor();
	}

	/**
	 * 默认支持String-Date类型转换
	 *
	 * @param registry registry
	 */
	@Override
	public void addFormatters(FormatterRegistry registry) {
		registry.addConverter(new Date2LocalDateConverter());
		registry.addConverter(new Date2LocalDateTimeConverter());
		registry.addConverter(new LocalDate2DateConverter());
		registry.addConverter(new LocalDateTime2DateConverter());
		registry.addConverter(new SqlDate2LocalDateConverter());
		registry.addConverter(new SqlDate2LocalDateTimeConverter());
		registry.addConverter(new String2DateConverter());
		registry.addConverter(new String2LocalDateConverter());
		registry.addConverter(new String2LocalDateTimeConverter());
		registry.addConverter(new String2BooleanConverter());
		registry.addConverter(new Timestamp2LocalDateTimeConverter());
	}

	@Override
	public void addInterceptors(@Nullable InterceptorRegistry registry) {
		if (interceptors != null) {
			interceptors.forEach(interceptor -> {
				String[] patten = interceptor.pathPatten().split(",");
				assert registry != null;
				InterceptorRegistration registration = registry.addInterceptor(interceptor).addPathPatterns(patten);
				List<String> excludePathPatterns = interceptor.excludePathPatterns();
				if (excludePathPatterns != null) {
					registration.addPathPatterns(excludePathPatterns);
				}
				if (log.isInfoEnabled()) {
					log.info("映射：{}，过滤器{}", patten, interceptor.getClass().getName());
				}
			});
		}
	}

}
